#include "socket.hh"

using namespace std;
using namespace NM;

Socket::Socket()
{
    fd = SOCKET_STATE_CLOSE;
    addr.setPort(SOCKET_DFLT_PORT);
	setType(TCP4);
    backlog = SOCKET_DFLT_BACKLOG;
}

Socket::Socket(enum Socket::type _type)
{
    fd = SOCKET_STATE_CLOSE;
    addr.setPort(SOCKET_DFLT_PORT);
	setType(_type);
    backlog = SOCKET_DFLT_BACKLOG;
}

Socket::Socket(enum Socket::type _type, const string &_address, int _port)
{
    fd = SOCKET_STATE_CLOSE;
	setType(_type);
	addr.setAddress(_address);
	addr.setPort(_port);
    backlog = SOCKET_DFLT_BACKLOG;
}

Socket::Socket(const Socket &_ref)
{
	if (&_ref != this)
	{
		backlog = _ref.backlog;
		type    = _ref.type;
		addr    = _ref.addr;
	}
}

Socket &Socket::operator=(const Socket &_ref)
{
	if (&_ref != this)
	{
		disconnect();

		backlog = _ref.backlog;
		type    = _ref.type;
		addr    = _ref.addr;
	}
	return *(this);
}

bool Socket::operator==(const Socket &_ref)
{
	if (&_ref == this){return true;}
	return false;
}

bool Socket::operator==(Socket *_ref)
{
	if (_ref == this){return true;}
	return false;
}


Socket::~Socket()
{
	disconnect();
}

void 				Socket::setType(enum Socket::type _type)
{
	type = _type;
	if (type == UDP4 || type == TCP4)
		addr.setDomain(AF_INET);
	else if (type == UDP6 || type == TCP6)
		addr.setDomain(AF_INET6);
}

void 				Socket::setAddress(const string &_address)
{
	addr.setAddress(_address);
}

void 				Socket::setName(const string &_name)
{
	addr.setName(_name);
}

void 				Socket::setPort(int _port)
{
	addr.setPort(_port);
}

void 				Socket::setBlock(bool _mode)
{
	int 			bmode = 1;

	if (_mode)
		bmode = 0;
	if (ioctl(fd, FIONBIO, &bmode) < 0)
        return;
}

void 				Socket::setBacklog(int _backlog)
{
	backlog = _backlog;
}

Saddr 				*Socket::getSaddr()
{
	return &addr;
}

void				Socket::socket()
{
	int 	optval 	= 1;
	//int 	mode 	= 0;

	if (type == TCP4 || type == TCP6)
		fd = ::socket(addr.getDomain(), SOCK_STREAM, 0);
	else if (type == UDP4 || type == UDP6)
		fd = ::socket(addr.getDomain(), SOCK_DGRAM, 0);
	if (this->fd < 0)
        return;

	// setsockopt() allow to reuse socket immediately after its close
	if (setsockopt(this->fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
        return;
}

void				Socket::bind()
{
    if (this->fd == SOCKET_STATE_CLOSE)
		socket();

	if (::bind(fd, addr.getSaddr(), addr.getSaddrSize()) != 0)
        return;
}

void 				Socket::bind(int _port)
{
	addr.setPort(_port);
	bind();
}

void				Socket::listen()
{
	if (::listen(fd, backlog) != 0)
        return;
}

void 				Socket::connect()
{
    if (fd == SOCKET_STATE_CLOSE)
		socket();

	if (::connect(fd, addr.getSaddr(), addr.getSaddrSize()) != 0)
        return;
}

void 				Socket::connect(const string &_address, int _port)
{
	addr.setAddress(_address);
	addr.setPort(_port);
	connect();
}

void 				Socket::disconnect()
{
	shutdown('a');
	// SHOULD read until an error is raised
    if (fd != SOCKET_STATE_CLOSE)
        ::close(fd);
}

void 				Socket::close()
{
	disconnect();
}

void 				Socket::shutdown(char c)
{
    if (fd != SOCKET_STATE_CLOSE)
	{
		switch(c)
		{
			case 'w':
				::shutdown(fd, SHUT_WR);
				break;
			case 'W':
				::shutdown(fd, SHUT_WR);
				break;
			case 'r':
				::shutdown(fd, SHUT_RD);
				break;
			case 'R':
				::shutdown(fd, SHUT_RD);
				break;
			default:
				::shutdown(fd, SHUT_RDWR);
				break;
		}
	}
}

Socket 			*Socket::accept()
{
    Socket 		*client = new Socket();
	unsigned int 	clen 	= client->addr.getSaddrSize();

	client->fd = ::accept(fd, client->addr.getSaddr(), &clen);
	if (client->fd < 0)
        return NULL;
	else
		return(client);
}

int 				Socket::receive(void *_buffer, int _size)
{
    int 			ret = -1;
    fd_set          readfs;
    int             ready;
    int             pos = 0;

    while (_size > 0)
    {
        ready = 0;
        FD_ZERO(&readfs);
        FD_SET(fd, &readfs);

        if (0 >= (ready = select(fd + 1, &readfs, NULL, NULL, NULL)))
        {
           perror("SOCKET: select()");
           disconnect();
           return (-1);
        }
        else if (FD_ISSET(fd, &readfs))
        {
            ret = recv(fd, (char*)_buffer + pos, _size, 0);
            if (ret < 0)
                return ret;
            else if (ret == 0)
                disconnect();
            pos += ret;
            _size -= ret;
        }
        else
            return (-1);
    }

	return ret;
}

int 				Socket::receive(int *_buffer)
{
    int 			htbuffer = 0;
	int 			ret;

	ret = receive(&htbuffer, sizeof(int));
	*_buffer = ntohl(htbuffer);
	return ret;
}

int 				Socket::receiveFrom(void *_buffer, int _size, Saddr &_addr)
{
	socklen_t 		from;
	int 			ret;

	from = _addr.getSaddrSize();
	ret = recvfrom(fd, _buffer, _size, 0, _addr.getSaddr(), &from);
	if (ret < 0)
        return ret;
	else if (ret == 0)
		disconnect();
	return ret;
}

int 				Socket::receiveFrom(int *_buffer, Saddr &_addr)
{
	int 			htbuffer;
	int 			ret;

	ret = receiveFrom(&htbuffer, sizeof(int), _addr);
	*_buffer = ntohl(htbuffer);
	return ret;
}

int 				Socket::send(void *_buffer, int _size)
{
    int 			ret = -1;
    fd_set          writefs;
    int             ready = 0;

    FD_ZERO(&writefs);
    FD_SET(fd, &writefs);

    if (0 >= (ready = select(fd + 1, NULL, &writefs, NULL, NULL)))
    {
       perror("SOCKET: select()");
       disconnect();
       return (-1);
    }
    else if (FD_ISSET(fd, &writefs))
    {
        ret = ::send(fd, _buffer, _size, 0);
        if (ret < 0)
            return ret;
        else if (ret == 0)
            disconnect();
    }

	return ret;
}

int 				Socket::send(const string &_buffer)
{
	const char 		*cstr;

	cstr = _buffer.c_str();
	return send((void*)cstr, _buffer.size());
}

int 				Socket::send(int _buffer)
{
	int 			htbuffer;

	htbuffer = htonl(_buffer);
	return send(&htbuffer, sizeof(int));
}

int 				Socket::sendTo(void *_buffer, int _size, Saddr &_addr)
{
	int 			ret;

	ret = ::sendto(fd, _buffer, _size, 0, _addr.getSaddr(), _addr.getSaddrSize());
		if (ret < 0)
            return ret;
	else if (ret == 0)
		disconnect();
	return ret;
}

int 				Socket::sendTo(const string &_buffer, Saddr &_addr)
{
	const char 		*cstr;

	cstr = _buffer.c_str();
	return sendTo((void*)cstr, _buffer.size(), _addr);	
}

int 				Socket::sendTo(int _buffer, Saddr &_addr)
{
	int 			htbuffer;

	htbuffer = htonl(_buffer);
	return sendTo(&htbuffer, sizeof(int), _addr);
}

void 				Socket::endpoint()
{
	bind();
	listen();
}

void 				Socket::endpoint(enum Socket::type _type, const string &_address, int _port)
{
	setType(_type);
	setAddress(_address);
	setPort(_port);
	endpoint();
}
